"""
    jupylet/sprite.py
    
    Copyright (c) 2020, Nir Aides - nir@winpdb.org

    Redistribution and use in source and binary forms, with or without
    modification, are permitted provided that the following conditions are met:

    1. Redistributions of source code must retain the above copyright notice, this
       list of conditions and the following disclaimer.
    2. Redistributions in binary form must reproduce the above copyright notice,
       this list of conditions and the following disclaimer in the documentation
       and/or other materials provided with the distribution.

    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
    ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
    WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
    DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
    ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
    (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
    LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
    ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
    (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
    SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
"""


import webcolors
import moderngl
import pathlib
import math
import glm

import PIL.Image

import moderngl_window as mglw
import numpy as np

from .collision import trbl, hitmap_and_outline_from_alpha, compute_collisions
from .resource import load_texture, pil_from_texture, get_shader_2d
from .utils import glm_dumps, glm_loads
from .color import c2v
from .state import State
from .node import Node, aa2q, q2aa
from .lru import SPRITE_TEXTURE_UNIT


_empty_array = np.array([])


class Sprite(Node):

    """一个2D游戏精灵(sprite)。
    
    Args:
        img: 指向精灵图像、PIL图像对象或包含图像像素数据的numpy阵列的路径。
        x (float): 精灵的x位置。
        y (float): 精灵的y位置。
        scale (float): 用于向上或向下缩放精灵的值。
        angle (float): 精灵以度为单位顺时针旋转角度值。
        anchor_x (float or str): 'left', 'center' , 'right' 或介于0.0（左）
            和1.0（右）之间的值，指示精灵内沿x轴的定位点。
        anchor_y (float or str): 'bottom', 'center' , 'top' 或介于0.0（底部）
            和1.0（顶部）之间的值，指示精灵内沿其y轴的定位点。
        color (str or 3-tuple): 用于为精灵图像着色的颜色。可以是颜色名称、
            颜色十六进制符号或三元组(3-tuple)。
        flip (bool): 是否上下颠倒精灵。
    """

    def __init__(
        self,
        img, 
        x=0, 
        y=0,
        scale=1.0,
        angle=0.0,
        anchor_x='center',
        anchor_y='center',
        color='white',
        flip=True, 
        mipmap=True, 
        autocrop=False,
        anisotropy=8.0, 
        height=None,
        width=None,
        name=None,
        collisions=True,
    ):
        """"""

        super().__init__(
            name,
            rotation=aa2q(glm.radians(angle)),
            scale=None,
            position=glm.vec3(x, y, 0),
        )

        self.mipmap = mipmap 
        self.autocrop = autocrop
        self.anisotropy = anisotropy
        
        self.geometry = mglw.geometry.quad_2d(
            size=(1.0, 1.0), 
            pos=(0.5, 0.5)
        )

        self.texture = load_texture(
            img,
            anisotropy=anisotropy, 
            autocrop=autocrop,
            mipmap=mipmap, 
            flip=False, 
        )
        self.texture.repeat_x = False
        self.texture.repeat_y = False

        self.baseline = 0
        self.components = self.texture.components
        self.color4 = glm.vec4(1., 1., 1., 1.)
        self.flip = flip

        self._collisions = collisions

        if self._collisions:
            self.hitmap, self.outline = hitmap_and_outline_from_alpha(self.image)
        
        self.scale = scale

        if width:
            self.width = width
        
        elif height:
            self.height = height

        self.set_anchor(anchor_x, anchor_y)
        self.color = color

    def update(self, shader):
        pass

    def draw(self, shader=None):
        """将精灵渲染到画布-这是 Sprite.render()的别名"""
        return self.render(shader)
        
    def render(self, shader=None):
        """将精灵渲染到画布.
        
        Args:
            shader (moderngl.program.Program, optional): 用于渲染的OpenGL着色器程序。
        """
        shader = shader or get_shader_2d()
        
        if self._dirty:
            self.update(shader)

        shader['components'] = self.components
        shader['color'].write(self.color4)
        shader['flip'] = self.flip

        shader['model'].write(self.matrix)
        shader['texture0'] = SPRITE_TEXTURE_UNIT

        self.texture.use(location=SPRITE_TEXTURE_UNIT)
        self.geometry.render(shader)

    @property
    def scale(self):
        """float: 精灵缩放比例。
        
        比例等于以像素为单位的渲染宽度除以纹理的实际宽度。例如，2的比例会使精灵看起来
        比原来大两倍，1/2的比例会使精灵看起来比原来小两倍。
        """
        return self.scale0.x / self.texture.width

    @scale.setter
    def scale(self, scale):        
        self.scale0 = scale * glm.vec3(self.texture.width, self.texture.height, 1)

    @property
    def x(self):
        """float: 锚定位置的x坐标。"""
        return self.position.x
        
    @x.setter
    def x(self, value):
        self.position.x = value
        
    @property
    def y(self):
        """float: 锚定位置的y坐标。"""
        return self.position.y
        
    @y.setter
    def y(self, value):
        self.position.y = value
        
    @property
    def angle(self):
        """float: 围绕锚点的旋转角度（以度为单位）。"""
        angle, axis = q2aa(self.rotation)
        return round(glm.degrees(angle * glm.sign(axis.z)), 4)

    @angle.setter
    def angle(self, angle):
        self.rotation = aa2q(glm.radians(angle))

    def set_anchor(self, ax=None, ay=None):
        """设置精灵的锚点。

        锚是精灵中用于旋转和定位的点。想象一个针穿过精灵，你用这个针在画布上定位
        精灵并旋转它。针穿过纹理的点是锚定点。
        
        分别为x轴和y轴设置定位点。

        Args:
            ax (str or float): x定位可以是 'left', 'center', 
                'right' 或介于0.0（左）和1.0（右）之间的浮点值之一。

            ay (str or float): y定位可以是 'bottom', 'center', 
                'top' 或介于0.0（底部）和1.0（顶部）之间的浮动值之一。
        """
        self._ax = ax
        self._ay = ay

        if ax == 'left':
            self.anchor.x = 0
        elif ax == 'center':
            self.anchor.x = 0.5
        elif ax == 'right':
            self.anchor.x = 1.
        elif type(ax) in (int, float):
            self.anchor.x = ax / self.width

        if ay == 'bottom':
            self.anchor.y = 0
        if ay == 'baseline':
            self.anchor.y = self.baseline
        elif ay == 'center':
            self.anchor.y = 0.5
        elif ay == 'top':
            self.anchor.y = 1.
        elif type(ay) in (int, float):
            self.anchor.y = ay / self.width

    @property
    def width(self):
        """float: 缩放后的宽度（以像素为单位）。"""
        return self.scale0.x

    @width.setter
    def width(self, width):
        self.scale0 = glm.vec3(self.texture.width, self.texture.height, 1) * width / self.texture.width

    @property
    def height(self):
        """float: 缩放后的高度（以像素为单位）。"""
        return self.scale0.y

    @height.setter
    def height(self, height):
        self.scale0 = glm.vec3(self.texture.width, self.texture.height, 1) * height / self.texture.height

    @property
    def image(self):
        """PIL.Image.Image: 精灵的位图图像。
        
        可以使用新图像或磁盘上图像的路径设置此属性，以更改当前的精灵图像。
        """
        return pil_from_texture(self.texture)
    
    @image.setter
    def image(self, img):
        
        scale = self.scale

        self.texture.release()
        self.texture = load_texture(
            img,
            anisotropy=self.anisotropy, 
            autocrop=self.autocrop,
            mipmap=self.mipmap, 
            flip=False, 
        )
        self.texture.repeat_x = False
        self.texture.repeat_y = False

        self.scale = scale

        if self._collisions:
            self.hitmap, self.outline = hitmap_and_outline_from_alpha(self.image)

    def collisions_with(self, o, debug=False):
        """计算与指定精灵的碰撞。
        
        Args:
            o (Sprite): 另一个用来检查碰撞的精灵。

        Returns:
            numpy.ndarray: 带有碰撞点的二维数组，如果精灵不碰撞，则为空数组。
        """

        #if self.distance_to(o) > self.radius + o.radius:
        #    return

        x0, y0 = self.position.xy
        x1, y1 = o.position.xy

        t0, r0, b0, l0 = self._trbl()
        t1, r1, b1, l1 = o._trbl()

        if t0 + y0 < b1 + y1 or t1 + y1 < b0 + y0:
            return _empty_array[:0]

        if r0 + x0 < l1 + x1 or r1 + x1 < l0 + x0:
            return _empty_array[:0]
        
        return compute_collisions(o, self, debug=debug)

    def distance_to(self, o=None, pos=None):
        """计算到另一个精灵或坐标的距离。

        Args:
            o (Sprite, optional): 其他要计算距离的精灵。
            pos (tuple, optional): 要计算距离位置的(x, y)坐标。

        Returns:
            float: 以像素为单位的距离。
        """
        x, y = pos or (o.position.x, o.position.y)
        
        dx = x - self.position.x
        dy = y - self.position.y

        return (dx ** 2 + dy ** 2) ** 0.5
    
    def angle_to(self, o=None, pos=None):
        """以度为单位计算与另一个精灵或坐标点的顺时针角度。

        Args:
            o (Sprite, optional): 其他要计算角度的精灵。
            pos (tuple, optional): 要计算角度的的(x, y)坐标点。

        Returns:
            float: 以度为单位的角度。
        """

        qd = {
            (True, True): 0,
            (True, False): 180,
            (False, False): 180,
            (False, True): 360,
        }
        
        x, y = pos or (o.position.x, o.position.y)
        
        dx = x - self.position.x
        dy = y - self.position.y

        a0 = math.atan(dy / (dx or 1e-7)) / math.pi * 180 + qd[(dy >= 0, dx >= 0)]

        return -a0

    @property
    def top(self):
        """float: 获取精灵边界框的顶部坐标。"""
        t, r, b, l = self._trbl()
        return self.position.y + t
        
    @property
    def right(self):
        """float: 获取精灵边界框的右部坐标。"""
        t, r, b, l = self._trbl()
        return self.position.x + r
        
    @property
    def bottom(self):
        """float: 获取精灵边界框的底部坐标。"""
        t, r, b, l = self._trbl()
        return self.position.y + b
        
    @property
    def left(self):
        """float: 获取精灵边界框的左部坐标。"""
        t, r, b, l = self._trbl()
        return self.position.x + l
        
    @property
    def radius(self):
        """float: 获取包含精灵边界框的圆的半径。"""
        t, r, b, l = self._trbl()
        rs = max(t, b) ** 2 + max(r, l) ** 2
        return rs ** .5
        
    def _trbl(self):
        tx = self.texture
        return trbl(
            tx.width, 
            tx.height, 
            self.anchor.x * tx.width, 
            self.anchor.y * tx.height, 
            self.angle,
            self.scale
        )

    def wrap_position(self, width, height, margin=50):
        """精灵的坐标环绕给定画布的宽度和高度。

        使用此方法可以让精灵从画布的另一侧消失后从画布的另一侧返回。

        Args:
            width (float): 要环绕的画布宽度。
            height (float): 要环绕的画布高度。
            margin (float, optional): 在将精灵包裹到另一侧之前，在画布周围添加额外的边距。
        """
        self.position.x = (self.position.x + margin) % (width + 2 * margin) - margin
        self.position.y = (self.position.y + margin) % (height + 2 * margin) - margin

    def clip_position(self, width, height, margin=0):
        """将精灵的坐标剪裁到给定的画布宽度和高度。

        使用此方法可防止精灵离开画布。

        Args:
            width (float): 要剪裁到的画布宽度。
            height (float): 要剪裁到的画布高度。
            margin (float, optional): 在剪切精灵坐标之前，在画布周围添加额外的边距。
        """
        self.position.x = max(-margin, min(margin + width, self.position.x))
        self.position.y = max(-margin, min(margin + height, self.position.y))

    @property
    def opacity(self):
        """float: 获取或设置精灵的不透明度。

        设置opacity为0将使精灵完全透明。
        设置opacity为1将使精灵完全不透明。
        """
        return self.color4.a

    @opacity.setter
    def opacity(self, opacity):
        self.color4.a = opacity
        
    @property
    def color(self):
        """glm.vec4: 获取或设置精灵的颜色。
        
        精灵颜色将乘以位图图像的颜色值。

        颜色可以通过名称(如 'white')或十六进制表示法(如 '#cc4488')或四元组(4-tuple)
        或 glm.vec4 来指定。
        """
        return self.color4

    @color.setter
    def color(self, color):        
        self.color4 = c2v(color, self.color4.a)

    def get_state(self):
        """获取定义对象状态的属性字典。
        
        Returns:
            dict: 属性值的字典对象。
        """
        return dict(
            node = super().get_state(),
            color4 = glm_dumps(glm.vec4(self.color4)),
            mipmap = self.mipmap,
            autocrop = self.autocrop,
            anisotropy = self.anisotropy,
            baseline = self.baseline,
            components = self.components,
            flip = self.flip,
            image = self.image,
        )

    def set_state(self, s):
        """根据指定的属性字典设置对象状态。
        
        Args:
            s (dict): 由 ``get_state()`` 获取的属性字典对象。
        """
        for k, v in s.items():
            if k != 'node':
                setattr(self, k, glm_loads(v))

        super().set_state(s['node'])

